package cn.ty.utils;

import java.lang.management.ManagementFactory;
import java.net.SocketException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * snowflake
 */
public class SnowflakeIdGenerator {

	private static final Logger LOGGER = LoggerFactory.getLogger(SnowflakeIdGenerator.class);

	// 时间起始标记点，作为基准，一般取系统的最近时间（一旦确定不能变动）
	// 这里设置为2001-01-01 00:00:01
	private static final long TWEPOCH = 978278401000L;

	// 机器标识位数
	private static final long WORKER_ID_BITS = 5L;

	// 数据中心标识位数
	private static final long DATACENTER_ID_BITS = 5L;

	// 机器ID最大值
	private static final long MAX_WORKER_ID = -1L ^ (-1L << WORKER_ID_BITS);

	// 数据中心ID最大值
	private static final long MAX_DATACENTER_ID = -1L ^ (-1L << DATACENTER_ID_BITS);

	// 毫秒内自增位
	private static final long SEQUENCE_BITS = 12L;

	// 数据中心ID左移17位
	// 机器ID偏左移12位
	private static final long WORKER_ID_SHIFT = SEQUENCE_BITS;

	private static final long DATACENTER_ID_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS;

	// 时间毫秒左移22位
	private static final long TIMESTAMP_LEFT_SHIFT =
			SEQUENCE_BITS + WORKER_ID_BITS + DATACENTER_ID_BITS;

	private static final long SEQUENCE_MASK = -1L ^ (-1L << SEQUENCE_BITS);

	/* 上次生产id时间戳 */
	private static long lastTimestamp = -1L;

	// 0，并发控制
	private long sequence = 0L;

	private final long workerId;

	// 数据标识id部分
	private final long datacenterId;

	public SnowflakeIdGenerator() {
		this.datacenterId = getDatacenterId(MAX_DATACENTER_ID);
		this.workerId = getMaxWorkerId(datacenterId, MAX_WORKER_ID);
	}

	/**
	 * 获取下一个ID
	 */
	public synchronized long nextId() {
		long timestamp = timeGen();
		if (timestamp < lastTimestamp) {
			throw new RuntimeException(
					String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds",
							lastTimestamp - timestamp)
			);
		}

		if (lastTimestamp == timestamp) {
			// 当前毫秒内，则+1
			sequence = (sequence + 1) & SEQUENCE_MASK;
			if (sequence == 0) {
				// 当前毫秒内计数满了，则等待下一秒
				timestamp = tilNextMillis(lastTimestamp);
			}
		} else {
			sequence = 0L;
		}
		lastTimestamp = timestamp;
		// ID偏移组合生成最终的ID，并返回ID
		return ((timestamp - TWEPOCH) << TIMESTAMP_LEFT_SHIFT)
				| (datacenterId << DATACENTER_ID_SHIFT)
				| (workerId << WORKER_ID_SHIFT) | sequence;
	}

	private long tilNextMillis(final long lastTimestamp) {
		long timestamp = this.timeGen();
		while (timestamp <= lastTimestamp) {
			timestamp = this.timeGen();
		}
		return timestamp;
	}

	private long timeGen() {
		return System.currentTimeMillis();
	}

	/**
	 * 获取 maxWorkerId
	 */
	private static long getMaxWorkerId(long datacenterId, long maxWorkerId) {
		StringBuilder sb = new StringBuilder();
		sb.append(datacenterId);
		String name = ManagementFactory.getRuntimeMXBean().getName();
		if (!name.isEmpty()) {
			/*
			 * GET jvmPid
			 */
			sb.append(name.split("@")[0]);
		}
		/*
		 * MAC + PID 的 hashcode 获取16个低位
		 */
		return (sb.toString().hashCode() & 0xffff) % (maxWorkerId + 1);
	}

	/**
	 * 数据标识id部分
	 */
	private static long getDatacenterId(long maxDatacenterId) {
		long id = 0L;
		try {
			byte[] mac = MacUtil.getMacAddress();
			if (mac.length == 0) {
				id = 1L;
			} else {
				id = ((0x000000FF & (long) mac[mac.length - 1])
						| (0x0000FF00 & (((long) mac[mac.length - 2]) << 8))) >> 6;
				id = id % (maxDatacenterId + 1);
			}

//            InetAddress ip = InetAddress.getLocalHost();
//            NetworkInterface network = NetworkInterface.getByInetAddress(ip);
//            if (network == null) {
//                id = 1L;
//            } else {
//                byte[] mac = network.getHardwareAddress();
//                id = ((0x000000FF & (long) mac[mac.length - 1])
//                        | (0x0000FF00 & (((long) mac[mac.length - 2]) << 8))) >> 6;
//                id = id % (maxDatacenterId + 1);
//            }
		}
		catch (SocketException e) {
			LOGGER.error("getDatacenterId: {}", e.getMessage());
		}
		return id;
	}

	private static SnowflakeIdGenerator instance;

	public static SnowflakeIdGenerator getInstance() {
		if (instance == null) {
			instance = new SnowflakeIdGenerator();
		}
		return instance;
	}
}
